import datetime
import functools
import os
import pandas

from coin.base.datetime_util import to_datetime

from coin.strategy.hk_hard_arb.unhedged_mm_3 import PassUnhedgeStrategyConfig
from coin.strategy.hk_hard_arb.util import load_clean_json
import coin.strategy.mm.simple_sim.profile.pass_unhedge_base as \
    pass_unhedge_base
from coin.strategy.mm.simple_sim.strategy.pass_unhedge_4 import (PassUnhedgedSimStrategy)
from coin.strategy.mm.simple_sim.util.strategy_util import (adjust_product_expiry,
                                                            get_machine_by_subreq,
                                                            get_post_only)
from coin.strategy.mm.simple_sim import result_util
from coin.strategy.accounting.fee import get_fee_rate

NS_PER_SECOND = 10**9
MS_PER_SECOND = 10**3
NS_PER_MINUTE = 60 * NS_PER_SECOND
PASS_UNHEDGE_STRAT_CONFIG = None


def _get_min_max_pos(subreq, reserve, stack, lot_size):
  min_pos = reserve - 0.5 * stack * lot_size
  max_pos = reserve + 0.5 * stack * lot_size
  if subreq.market_type == 'Spot':
    assert min_pos >= 0 and max_pos >= 0
  return min_pos, max_pos


def _convert_strat_config(config, edge_bp, basis_ma_window_min):
  config = config.copy()
  config['edge_bp'] = edge_bp
  config['basis_ma_window_min'] = basis_ma_window_min
  return config


def prepare(args, ref_ts):
  pass_unhedge_base.prepare(args, ref_ts)
  config_path = os.path.join('coin/strategy/mm/simple_sim/pass_unhedge_config',
                             pass_unhedge_base.P_FLAGS.config_filename)
  global PASS_UNHEDGE_STRAT_CONFIG
  PASS_UNHEDGE_STRAT_CONFIG = PassUnhedgeStrategyConfig(load_clean_json(config_path))


def get_products(from_ts):
  strat_config = PASS_UNHEDGE_STRAT_CONFIG
  products = []
  for profile in strat_config.model_profile.values():
    trade_product = adjust_product_expiry(profile.trade_product, from_ts)
    products.append(trade_product)
    for ref_product in profile.ref_product:
      ref_product = adjust_product_expiry(ref_product, from_ts)
      products.append(ref_product)
  return products


def get_trading_dates(trading_date_str):
  if trading_date_str.find("-") >= 0:
    tdfrom, tdto = trading_date_str.split("-")
    return pandas.date_range(tdfrom, tdto).to_pydatetime()
  else:
    return [datetime.datetime.strptime(trading_date_str, '%Y%m%d')]


def get_time_ranges():
  ranges = []
  trade_dates = get_trading_dates(pass_unhedge_base.P_FLAGS.date)
  cur_dt = datetime.datetime.combine(trade_dates[0], datetime.datetime.min.time())
  end_dt = datetime.datetime.combine(trade_dates[-1], datetime.datetime.min.time())
  while cur_dt < end_dt:
    ranges.append((cur_dt, cur_dt + datetime.timedelta(hours=24)))
    cur_dt += datetime.timedelta(hours=24)
  return ranges


def get_execution_delay():
  return [0.25]


def get_machines():
  return get_machine_by_subreq(PASS_UNHEDGE_STRAT_CONFIG.trade_subreq)


def get_strategy(from_ts, to_ts):
  strategy_list = []
  strat_config = PASS_UNHEDGE_STRAT_CONFIG
  for profile in strat_config.model_profile.values():
    trade_subreq = profile.trade_subreq
    trade_product = adjust_product_expiry(profile.trade_product, from_ts)
    ref_product = [adjust_product_expiry(product, from_ts) for product in profile.ref_product]
    maker_fee, taker_fee = get_fee_rate(trade_subreq.market_type, trade_subreq.exchange)
    post_only = get_post_only(trade_subreq.market_type, trade_subreq.exchange)
    edge_func = lambda edge, pos: edge
    single_config = profile.config_dict

    pull_edge = None
    pull_edge_bp = single_config.get('pull_edge_bp', None)
    if pull_edge_bp is not None:
      assert pull_edge_bp >= 0
      pull_edge = pull_edge_bp / 10000.

    for basis_ma_window in single_config['basis_ma_window_min']:
      for edge_bp in single_config['edge_bp']:
        for agg_edge in [None]:
          for close_edge_bp in [edge_bp]:
            for stack in single_config['stack']:
              for delay in get_execution_delay():
                delay_in_ns = delay * NS_PER_SECOND
                delay_in_ms = delay * MS_PER_SECOND
                reserve = single_config['reserve']
                lot_size = single_config['lot_size']
                min_pos, max_pos = _get_min_max_pos(profile.trade_subreq, reserve, stack, lot_size)
                executor_param = {
                    'lot_size': lot_size,
                    'min_pos': min_pos,
                    'max_pos': max_pos,
                    'maker_fee': maker_fee,
                    'taker_fee': taker_fee,
                    'execution_delay': delay_in_ns,
                    'post_only': post_only,
                    'trade_qty_func': (lambda trade: trade.qty)
                }
                pricing_param = _convert_strat_config(single_config,
                                                      edge_bp=edge_bp,
                                                      basis_ma_window_min=basis_ma_window)

                use_agg = 'without_agg'
                if agg_edge:
                  use_agg = 'with_agg'
                name = \
                  '%02dm.%02dbp.%02dbp.%02dstack.%04ddelay.%s.%s' % (
                    basis_ma_window, edge_bp, close_edge_bp, stack,
                    delay_in_ms,  use_agg,
                    to_datetime(from_ts).strftime('%Y%m%d'))

                # no need to set |trade_after| to basis_ma_window, as strategy
                # does wait this period of time in live trading.
                strategy = PassUnhedgedSimStrategy(trade_product,
                                                   ref_product,
                                                   functools.partial(edge_func, edge_bp / 10000.),
                                                   functools.partial(edge_func, edge_bp / 10000.),
                                                   pricing_param,
                                                   executor_param,
                                                   trade_after=0,
                                                   name=name,
                                                   agg_edge=agg_edge,
                                                   feature_filepath=('out/feature.%s.csv' % name),
                                                   fill_filepath=('out/fill.%s.csv' % name),
                                                   pull_edge=pull_edge)
                strategy_list.append(strategy)

  return strategy_list


def get_strategy_result(strategy):
  return {'name': strategy.name, **strategy.get_summary()}


def aggregate_result(results):
  return result_util.aggregate_sim_result(results)
